package com.github.blanexie.nexusj.domain.post

import cn.hutool.core.convert.Convert
import cn.hutool.core.date.DateUtil
import cn.hutool.core.io.FileUtil
import cn.hutool.crypto.digest.DigestUtil
import com.dampcake.bencode.Type
import com.github.blanexie.nexusj.bencode.bencode
import com.github.blanexie.nexusj.domain.peer.Size
import com.github.blanexie.nexusj.support.UTF8
import java.io.File
import java.nio.ByteBuffer
import java.nio.charset.Charset
import java.time.LocalDateTime
import java.time.ZoneOffset

/**
 * torrent 聚合
 */
data class TorrentAggregate(
    val torrentId: TorrentId,
    val showName: String,
    val rate: Ration,
    val postId: PostId,

    val announce: Announce,            //： Tracker的主服务器
    val comment: Comment,              //：种子文件的注释
    val creationDate: LocalDateTime,   //：种子文件建立的时间，是从1970年1月1日00:00:00到现在的秒数。
    val encoding: Charset,             //：种子文件的默认编码，比如GB2312，Big5，utf-8等

    val nodes: List<String>?,          //：这个字段包含一系列ip和相应端口的列表，是用于连接DHT初始node

    val info: Info,

    val status: Int = 0,
    val updateTime: LocalDateTime = LocalDateTime.now(),
    val createTime: LocalDateTime = LocalDateTime.now(),
) {

    /**
     * 生成文件.
     */
    fun download(): ByteArray {
        val infoMap = hashMapOf<String, Any>()
        infoMap["name"] = info.name
        info.name.nameUtf8?.let {
            infoMap.put("name.utf-8", it)
        }
        info.pieces.pieceLength?.let {
            infoMap.put("piece length", it)
        }
        info.pieces.pieces?.let {
            infoMap.put("pieces", it.array())
        }
        info.publisher?.let {
            infoMap.put("publisher", it)
        }
        info.publisher.publisherUtf8?.let {
            infoMap.put("publisher.utf-8", it)
        }
        info.publisher.publisherUrl?.let {
            infoMap.put("publisher-url", it)
        }
        info.publisher.publisherUrlUtf8?.let {
            infoMap.put("publisher-url.utf-8", it)
        }
        if (info.files == null || info.files.isEmpty()) {
            info.files?.let { it ->
                val list = it.map {
                    mapOf("path" to it.path, "length" to it.length, "path.utf-8" to it.pathUtf8)
                }
                infoMap.put("files", list)
            }
        } else {
            info.length?.let {
                infoMap.put("length", it)
            }
        }
        val torrentMap = mapOf(
            "announce" to announce.announce,
            "announce-list" to announce.announceList,
            "comment" to comment.comment,
            "comment.utf-8" to comment.commentUtf8,
            "creation date" to creationDate.toEpochSecond(ZoneOffset.ofHours(8)),
            "encoding" to encoding.toString(),
            "nodes" to nodes,
            "info" to infoMap
        )
        return bencode.encode(torrentMap)
    }
}


object TorrentFactory {

    fun create(postId: PostId, file: File, ration: Ration, showName: String?): TorrentAggregate {
        val torrentMap = bencode.decode(FileUtil.readBytes(file), Type.DICTIONARY)
        val infoMap = torrentMap["info"]!!.toMap()
        //计算infohash值
        val infoBytes = bencode.encode(infoMap)
        val infoHash = DigestUtil.sha1Hex(infoBytes)
        val announce = torrentMap["announce"]!!.toStr()
        val announceList = torrentMap["announce-list"]?.toListStr()
        val comment = torrentMap["comment"]?.toStr()
        val commentUtf8 = torrentMap["comment.utf-8"]?.toStr()
        val creationDate = torrentMap["creation_date"]?.toLong() ?: DateUtil.date().time

        val encoding = torrentMap["encoding"]?.toStr() ?: "utf8"
        val nodes = torrentMap["nodes"]?.toListStr()

        val name: String = infoMap["name"]!!.toStr()
        val nameUtf8: String? = infoMap["name.utf-8"]?.toStr()
        val pieceLength: Long = infoMap["piece length"]!!.toLong()
        val pieces: ByteBuffer = infoMap["pieces"] as ByteBuffer
        val publisher: String? = infoMap["publisher"]?.toStr()
        val publisherUtf8: String? = infoMap["publisher.utf-8"]?.toStr()
        val publisherUrl: String? = infoMap["publisher-url"]?.toStr()
        val publisherUrlUtf8: String? = infoMap["publisher-url.utf-8"]?.toStr()
        val files = infoMap["files"]?.toList()?.map {
            it?.toMap().run {
                val path = this!!["path"]!!.toListStr()
                val pathUtf8 = this["path.utf-8"]?.toListStr()
                val length = this["length"]!!.toLong()
                Files(length, path, pathUtf8)
            }
        }
        val length: Long = infoMap["length"]?.toLong() ?: files!!.sumOf { it.length }

        val info = Info(
            Size(length), files, Name(name, nameUtf8), Pieces(pieces, pieceLength),
            Publisher(publisher, publisherUtf8, publisherUrl, publisherUrlUtf8)
        )

        return TorrentAggregate(
            TorrentId(infoHash), showName ?: info.name.name, ration, postId,
            Announce(announce, announceList), Comment(comment.let { "" }, commentUtf8),
            LocalDateTime.ofEpochSecond(creationDate, 0, ZoneOffset.ofHours(8)),
            charset(encoding), nodes, info
        )
    }
}

data class TorrentId(val infoHash: String)
data class Comment(val comment: String, val commentUtf8: String?)
data class Announce(val announce: String, val announceList: List<String>?)

/* 倍率 */
data class Ration(
    val rate: Float,
    val start: LocalDateTime,
    val end: LocalDateTime
)

/**
 * 种子中的info信息
 */
data class Info(
    var length: Size,        //：文件的总大小（Byte）
    val files: List<Files>?, //多文件中的文件 files 和 length 只能有一个
    val name: Name,          //：推荐的文件夹名，此项可于下载时更改
    val pieces: Pieces,      //：文件的特征信息（将所有文件按照piece length的字节大小分成块，每块计算一个SHA1值，然后将这些值连接起来所组成）
    val publisher: Publisher
) {
    init {
        if (length == null && (files == null || files.isEmpty())) {
            throw Exception("种子文件中的info的格式不正确")
        }
    }
}

data class Name(val name: String, val nameUtf8: String?)
data class Pieces(val pieces: ByteBuffer, val pieceLength: Long)
data class Publisher(
    val publisher: String?,
    val publisherUtf8: String?,
    val publisherUrl: String?,
    val publisherUrlUtf8: String?,
)

/**
 * 种子中的info信息
 */
data class Files(
    val length: Long,
    val path: List<String>,
    val pathUtf8: List<String>?,//文件名的UTF-8编码，同上
)


fun Any.toLong(): Long {
    return Convert.toLong(this)
}

fun Any.toStr(): String {
    if (this is ByteBuffer) {
        return UTF8.decode(this).toString()
    }
    return toString()
}

fun Any.toListStr(): List<String> {
    if (this is List<*>) {
        return this.map { it!!.toStr() }
    }
    throw Exception("类型不对,无法转换")
}

fun Any.toList(): List<*> {
    if (this is List<*>) {
        return this
    }
    throw Exception("类型不对,无法转换")
}

fun Any.toMap(): Map<String, *> {
    val ret = hashMapOf<String, Any>()
    if (this is Map<*, *>) {
        this.forEach { k, v ->
            ret[k!!.toStr()] = v!!
        }
        return ret
    }
    throw Exception("类型不对,无法转换")
}
